#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os

from abc import abstractmethod

from ctools.converters.convbase import NConverterFilterBase
from ctools.ly_parser.nodes import (LinkNode,)
from ctools.datahelper.functree import TreeProvider

from ctools.common.strhelper import *

import logging
from ctools.logext import *

_rlog, _plog, _mlog, _tlog = get_loggers(__file__, __name__, propagate=False)

def _linknode_name(node:str):
    if '.' not in node:
        return node
    node = node.replace('.', ':', 1)
    ns = node.split(':')
    return '{}:"{}"'.format(ns[0], ns[1])

def _get_right_width(node):
    right_width = 0
    left = node.name
    for nd in node:
        right = nd.name
        if len(right) > right_width: right_width = len(right)

    return right_width

class ConvLinkNodeBase(NConverterFilterBase):
    def __init__(self, tree_prov:TreeProvider=None, ifilter=None):
        super().__init__(ifilter)
        self._tree_prov = tree_prov

    def set_tree_provider(self, tree_prov:TreeProvider=None):
        self._tree_prov = tree_prov

    def _check_simple(self, nd:LinkNode):
        left = _linknode_name(nd.left)
        right = _linknode_name(nd.right)

        if '.' in (left + right) or nd.jump == 0:
            if nd.jump > 0:
                _mlog.warning('unexpected dot, {} -> {} jump: {}'.format(left, right, nd.jump))
            return True

        if nd.jump > 0 and (self._tree_prov is None):
            _mlog.warning('{} -> {} jump: {} but no tree_prov'.format(left, right, nd.jump))
            return True
        return False

    @abstractmethod
    def _process_one_linknode(self, left, right, args:dict, ctrl_direction:str=''):
        return ''

    def _process_callee(self, node, args, depth):
        ctrl_direction  = args.get('ctrl_direction', '')
        sequence        = args.get("sequence", True)
        depth           = args.get("depth", depth)

        rightw = _get_right_width(node)

        args["rightw"] = rightw

        text_list = []
        callees = []
        left = node.name
        for nd in node:
            right = nd.name
            if not self._filter_item(right):
                continue

            if sequence:
                callees.append(right)

            text = indent_str(depth,
                        self._process_one_linknode(left, right, args, ctrl_direction))
            text_list.append(text)

        if args['text'] and not args['text'].endswith('\n'):
            args['text'] += '\n'
        args['text'] += '\n'.join(text_list)

        if sequence and len(callees) > 1:
            join_str = '\n' + indent_str(depth + 1, ' -> ')
            text = indent_str(depth, '{} rank=same '.format('{'))
            text += join_str.join(callees)
            text += '\n'
            text += indent_str(depth, ' [style=invis] {};'.format('}'))
            args['text'] += '\n' + text

    def process_linknode(self, nd:LinkNode, args, indent=1, **kwargs):
        left = _linknode_name(nd.left)
        right = _linknode_name(nd.right)
        if self._check_simple(nd):
            ctrl_direction = args.get('ctrl_direction', '')
            _tlog.debug(f'{left} -> {right}, linop: {nd.linkop}, ctrl_direction: {ctrl_direction}')
            text = self._process_one_linknode(left, right, args, ctrl_direction)
            return text

        # TODO: tree_prov
        tnode = self._tree_prov.get_tree(start=left, end=right,
                                         level=nd.jump, direction=args['tree_dir'],
                                         unique=args['unique'])
        if tnode:
            tnode.iter_nodes(self._process_callee, args)
        text = args['text']
        return text

class ConvLinkN2C(ConvLinkNodeBase):
    def _process_one_linknode(self, left, right, args:dict, ctrl_direction:str=''):
        _mlog.debug(args)
        rightw = args.get("rightw", 0)
        padding = ""
        if rightw > len(left):
            padding = ' ' * (rightw - len(right))
        _mlog.debug(f"rightw: {rightw}, len({right}): {len(right)}, padding: {padding}")

        lpad, rpad = '', ''

        linkop = args['linkop']
        _mlog.debug('{} -> {}, ctrl_direction: {}'.format(left, right, ctrl_direction))
        if '>' in ctrl_direction or ctrl_direction == 'auto':
            if '<' in linkop:
                linkop = linkop.replace('<', '>')
                left, right = right, left
                lpad = padding
        elif '<' in ctrl_direction:
            if '>' in linkop:
                linkop = linkop.replace('>', '<')
                left, right = right, left
                lpad = padding

        text = f'{left} {lpad}{linkop}{rpad} {right};'
        return text

    def process_linknode(self, nd:LinkNode, indent=1, **kwargs):
        ctrl_direction = kwargs.get('ctrl_direction', '')
        tree_dir = '->' if nd.dir not in ('back',) else '<-'
        args = {
            'text'          : '',
            'depth'         : indent,       # force indent
            'linkop'        : nd.linkop,
            'ctrl_direction': ctrl_direction,
            'sequence'      : False,        # sequence is not supported in c
            'tree_dir'      : tree_dir,
            'unique'        : kwargs.get('unique', False),
        }

        return super().process_linknode(nd, args, indent, **kwargs)

    process = process_linknode
    convert = process_linknode

class ConvLinkN2Gv(ConvLinkNodeBase):
    def _process_one_linknode(self, left, right, args:dict, ctrl_direction:str=''):
        _mlog.debug('{} -> {}, ctrl_direction: {}'.format(left, right, ctrl_direction))

        linkdir, style = args['dir'], args['style']

        if '>' in ctrl_direction or ctrl_direction == 'auto':
            if linkdir == 'back':
                linkdir = 'forward'
                left, right = right, left
        elif '<' in ctrl_direction:
            if linkdir == 'forward':
                linkdir = 'back'
                left, right = right, left

        text = '{} -> {}'.format(left, right)
        text += ' ['
        text += 'dir={}, '.format(linkdir)
        text += 'style={}, '.format(style)
        text += '];'
        return text

    def process_linknode(self, nd:LinkNode, indent=1, **kwargs):
        ctrl_direction = kwargs.get('ctrl_direction', '')
        sequence = kwargs.get('sequence', True)
        tree_dir = '->' if nd.dir not in ('back',) else '<-'
        if '<' in tree_dir:     # force set
            sequence = False

        args = {
            'text'          : '',
            'depth'         : indent,       # force indent
            'dir'           : nd.dir,
            'style'         : nd.style,
            'ctrl_direction': ctrl_direction,
            'sequence'      : sequence,
            'tree_dir'      : tree_dir,
            'unique'        : kwargs.get('unique', True),
        }

        return super().process_linknode(nd, args, indent, **kwargs)

    process = process_linknode
    convert = process_linknode
